// include the basic windows header file
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <stdint.h>

#include <d3d11.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <DirectXColors.h>

#include <iostream>

using namespace DirectX;
using namespace DirectX::PackedVector;

const uint32_t SCREEN_WIDTH = 960;
const uint32_t SCREEN_HEIGHT = 480;

// global declarations
IDXGISwapChain          *g_pSwapchain = nullptr;
ID3D11Device            *g_pDev       = nullptr;
ID3D11DeviceContext     *g_pDevcon    = nullptr;

ID3D11RenderTargetView  *g_pRTView    = nullptr;

ID3D11InputLayout       *g_pLayout    = nullptr;
ID3D11VertexShader      *g_pVS        = nullptr;
ID3D11PixelShader       *g_pPS        = nullptr;

ID3D11Buffer            *g_pVBuffer   = nullptr;

// vertex buffer structure
struct VERTEX {
    XMFLOAT3 Position;
    XMFLOAT4 Color;
};

template<class T>
inline void SafeRelease(T **ppInterfaceToRelease) 
{
    if (*ppInterfaceToRelease != nullptr) 
    {
        (*ppInterfaceToRelease)->Release();
         (*ppInterfaceToRelease) = nullptr;
    }
}

// 创建画布
void CreateRenderTarget() {
    HRESULT hr;
    ID3D11Texture2D *pBackBuffer;

    // Get a point to the back buffer
    g_pSwapchain->GetBuffer(0, __uuidof(ID3D11Texture2D),
                                (LPVOID*) &pBackBuffer);

    // create a render-target view
    g_pDev->CreateRenderTargetView(pBackBuffer, nullptr,
                                    &g_pRTView);

    pBackBuffer->Release();

    // Bind the view
    g_pDevcon->OMSetRenderTargets(1, &g_pRTView, NULL);
}

// 设置视口
void SetViewPort() 
{
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = SCREEN_WIDTH;
    viewport.Height = SCREEN_HEIGHT;

    g_pDevcon->RSSetViewports(1, &viewport);
}

// 初始化渲染管道
// this is the function that loads and prepares the shaders
void InitPipeline() 
{
    // load and compile the two shader
    ID3DBlob *VS, *PS;

    D3DReadFileToBlob(L"copy.vso", &VS);
    D3DReadFileToBlob(L"copy.pso", &PS);

    // encapsulate both shaders into shader objects
    g_pDev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &g_pVS);
    g_pDev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &g_pPS);

    // set the shader objects
    g_pDevcon->VSSetShader(g_pVS, 0, 0);
    g_pDevcon->PSSetShader(g_pPS, 0, 0);

    // create the input layout object
    D3D11_INPUT_ELEMENT_DESC ied[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,  0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
        { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
    };

    g_pDev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &g_pLayout);
    g_pDevcon->IASetInputLayout(g_pLayout);

    VS->Release();
    PS->Release();
}

void InitGraphics() 
{
    // create a triangle using the VERTEX struct
    VERTEX OutVertices[] = 
    {
        { XMFLOAT3(0.f, 0.5f, 0.f), XMFLOAT4(1.f, 0.f, 0.f, 1.f)},
        { XMFLOAT3(0.45f, -0.5f, 0.f), XMFLOAT4(0.f, 1.f, 0.f, 1.f)},
        { XMFLOAT3(-0.45f, -0.5f, 0.f), XMFLOAT4(0.f, 0.f, 0.f, 1.f)},
    };

    // create the vertex buffer
    D3D11_BUFFER_DESC bd;
    ZeroMemory(&bd, sizeof(bd));

    bd.Usage = D3D11_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(VERTEX) * 3;
    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    g_pDev->CreateBuffer(&bd, NULL, &g_pVBuffer);

    // copy the vertices into the buffer
    D3D11_MAPPED_SUBRESOURCE ms;
    g_pDevcon->Map(g_pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);       // map the buffer
    memcpy(ms.pData, OutVertices, sizeof(VERTEX) * 3);
    g_pDevcon->Unmap(g_pVBuffer, NULL);
}

// 创建绘图需要资源
HRESULT CreateGraphicsResources(HWND hWnd)
{
    // A 32-bit value that is used to describe an error or warning.
    HRESULT hr = S_OK;
    if (g_pSwapchain == nullptr) 
    {
        // create a struct to hold information about the swap chain
        DXGI_SWAP_CHAIN_DESC scd;

        // clear out the struct for use
        ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

        // fill the swap chain description   struct
        scd.BufferCount = 1;
        scd.BufferDesc.Width = SCREEN_WIDTH;
        scd.BufferDesc.Height = SCREEN_HEIGHT;
        scd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        scd.BufferDesc.RefreshRate.Numerator = 60;
        scd.BufferDesc.RefreshRate.Denominator = 1;
        scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        scd.OutputWindow = hWnd;
        scd.SampleDesc.Count = 4;
        scd.Windowed = TRUE;
        scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

        const D3D_FEATURE_LEVEL FeatureLevels[] = {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
            D3D_FEATURE_LEVEL_9_3,
            D3D_FEATURE_LEVEL_9_2,
            D3D_FEATURE_LEVEL_9_1,
        };

        D3D_FEATURE_LEVEL FeatureLevelSupported;
        
        // create a device, device context and swap chain using the information in the scd struct
        hr = D3D11CreateDeviceAndSwapChain(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            NULL,
            0,
            FeatureLevels,
            _countof(FeatureLevels),
            D3D11_SDK_VERSION,
            &scd,
            &g_pSwapchain,
            &g_pDev,
            &FeatureLevelSupported,
            &g_pDevcon
        );

        if (hr == E_INVALIDARG) {
            hr = D3D11CreateDeviceAndSwapChain(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            NULL,
            0,
            &FeatureLevelSupported,
            1,
            D3D11_SDK_VERSION,
            &scd,
            &g_pSwapchain,
            &g_pDev,
            NULL,
            &g_pDevcon
            );
        }

        if (hr == S_OK) {
            std::cout << " hr == S_OK " << std::endl;
            CreateRenderTarget();
            SetViewPort();
            InitPipeline();
            InitGraphics();
        } else {
            std::cout << " hr == S_OK " << std::endl;
        }
    }

    return hr;
}

void DiscardGraphicsResources() 
{
    SafeRelease(&g_pLayout);
    SafeRelease(&g_pVS);
    SafeRelease(&g_pPS);
    SafeRelease(&g_pVBuffer);
    SafeRelease(&g_pSwapchain);
    SafeRelease(&g_pRTView);
    SafeRelease(&g_pDev);
    SafeRelease(&g_pDevcon);
}


// this is the function used to render a single frame
void RenderFrame()
{
    // clear the back buffer to a deep blue
    const FLOAT clearClor[] = {1.f, 0.0f, 0.0f, 1.0f};
    g_pDevcon->ClearRenderTargetView(g_pRTView, clearClor);

    // do 3D rendering on the back buffer here
    {
        // select which vertex buffer to display
        UINT stride = sizeof(VERTEX);
        UINT offset = 0;
        g_pDevcon->IASetVertexBuffers(0, 1, &g_pVBuffer, &stride, &offset);

        // select which primtive type we are using
        g_pDevcon->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        // draw the vertex buffer to the back buffer
        g_pDevcon->Draw(3, 0);
    }

    // swap the back buffer and the front buffer
    g_pSwapchain->Present(0, 0);
}

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd,
                            UINT message,
                            WPARAM wParam,
                            LPARAM lParam);

// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPreInstance,
                   LPTSTR lpCmdLine,
                   int nCmdShow){
    // the handle for the window, filled by a function
    HWND hWnd;
    // this struct holds information for the window class
    WNDCLASSEX wc;

    // clear out the window class for use
    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    // fill in the struct with the needed information
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = _T("WindowClass1");

    // register the window class
    RegisterClassEx(&wc);

    // create the window and use the result as the handle
    hWnd = CreateWindowEx(0,
                          _T("WindowClass1"),   // name of the window
                          _T("Hello, Engine![Direct 3D]"), // title of the window
                          WS_OVERLAPPEDWINDOW,  // window style
                          100,                  // x positon of the window    
                          100,                  // y positon of the window
                          SCREEN_WIDTH,         // width of the window
                          SCREEN_HEIGHT,        // height of the window
                          NULL,                 // we have no parent window, NULL
                          NULL,                 // we aren't using menus, null
                          hInstance,            // application handle
                          NULL);                // used with multiple windows, NULL
    
    // display the window on the screen
    ShowWindow(hWnd, nCmdShow);
    
    // enter the main loop:

    // the struct holds Windows event messages
    MSG msg;

    // wait for the next message in the queue, store the result in "msg"
    while(GetMessage(&msg, nullptr, 0, 0)) {
        // translate keystroke messages into the right format
        TranslateMessage(&msg);

        // send the message to the WindowProc function
        DispatchMessage(&msg);
    }

    // return this part of the WM_QUIT message to Windows
    return msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hWnd,
                            UINT message,
                            WPARAM wParam,
                            LPARAM lParam){
    LRESULT result = 0;
    bool wasHandled = false;

    switch (message)
    {
        case WM_CREATE:
            wasHandled = true;
            break;
        // sort through and find what code to run for the message given
        case WM_PAINT:
            result = CreateGraphicsResources(hWnd);
            RenderFrame();
            wasHandled = true;
            break;
        case WM_SIZE:
            if (g_pSwapchain != nullptr)
            {
                DiscardGraphicsResources();
            } 
            wasHandled = true;
            break;
        // this message is read when the window is closed
        case WM_DESTROY:
            {
                DiscardGraphicsResources();
                PostQuitMessage(0);
                wasHandled = true;
            }
            break;

        // 显示器分辨率发生变化
        case WM_DISPLAYCHANGE:
            // 通知系统窗口的客户区域(Client Rect)需要进行重新绘制
            InvalidateRect(hWnd, nullptr, false);
            wasHandled = true;
            break;
    }


    if (!wasHandled) {
        result = DefWindowProc(hWnd, message, wParam, lParam);
    }
    // Handle any message the switch statement didn't 
    return result;
}